#ifndef PACKETREADER_H
#define PACKETREADER_H

#include <string>
#include <vector>
#include <set>
#include <map>
#include <boost/thread/thread.hpp>

class PacketCollector;
class PacketFilter;
class PacketListener;
class XMPPConnection;
class XmlPullParser;
class ConnectionListener;
class XMLParser;
class IQ;
class Authentication;
class RosterPacket;
class Registration;
class Bind;
class Packet;


/**
 * Listens for XML traffic from the XMPP server and parses it into packet objects.
 * The packet reader also manages all packet listeners and collectors.<p>
 *
 * @see PacketCollector
 * @see PacketListener
 * @author Matt Tucker
 */
class PacketReader { 
public:

	PacketReader( XMPPConnection * connection);

    /**
     * Initializes the reader in order to be used. The reader is initialized during the
     * first connection and when reconnecting due to an abruptly disconnection.
     */
	void init() ;

	/**
     * Creates a new packet collector for this reader. A packet filter determines
     * which packets will be accumulated by the collector.
     *
     * @param packetFilter the packet filter to use.
     * @return a new packet collector.
     */
    PacketCollector * createPacketCollector(PacketFilter * packetFilter) ;

    /**
     * Registers a packet listener with this reader. A packet filter determines
     * which packets will be delivered to the listener.
     *
     * @param packetListener the packet listener to notify of new packets.
     * @param packetFilter the packet filter to use.
     */
    void addPacketListener(PacketListener * packetListener, PacketFilter * packetFilter) ;
	
    /**
     * Removes a packet listener.
     *
     * @param packetListener the packet listener to remove.
     */
    void removePacketListener(PacketListener * packetListener) ;
	
    /**
     * Starts the packet reader thread and returns once a connection to the server
     * has been established. A connection will be attempted for a maximum of five
     * seconds. An XMPPException will be thrown if the connection fails.
     *
     * @throws XMPPException if the server fails to send an opening stream back
     *      for more than five seconds.
     */
    void startup() ;
	
    /**
     * Shuts the packet reader down.
     */
    void shutdown() ;
	
    /**
     * Cleans up all resources used by the packet reader.
     */
    void cleanup() ;

	void notifyReconnection();

    /**
     * Sends out a notification that there was an error with the connection
     * and closes the connection.
     *
     * @param e the exception that causes the connection close event.
     */
    void notifyConnectionError(std::string & err) ;

	bool hasConnectionListener(ConnectionListener * connectionListener);

	void addConnectionListener(ConnectionListener * connectionListener);

	void removeConnectionListener(ConnectionListener * connectionListener);


	std::set<ConnectionListener*> & getConnectionListeners()
	{
		return connectionListeners;
	}
	
//protected:


	void cancelPacketCollector(PacketCollector * packetCollector) ;
	
private:
	class ListenerWrapper {
	public:
		ListenerWrapper(PacketListener * packetListener, PacketFilter * packetFilter) :
			packetListener(packetListener),
			packetFilter(packetFilter)
		{}
       
        void notifyListener(Packet * packet) ;
	private:
		PacketListener * packetListener;
        PacketFilter * packetFilter;
    };


	
	class PacketParser;

	typedef PacketParser* (*PacketParserCreateFunc)(const std::string & name);

	template<typename T>
	class PacketParserCreator {
	public:
		static PacketParser* Create(std::string & name) {
			return new T(name);
		}
	};

	class PacketParserFactory : public std::map<std::string, PacketParserCreateFunc> {
	public:
		static PacketParserFactory & getInstance() {
			return _instance;
		}
		static void set(const std::string& name, PacketParserCreateFunc packetParserCreator) {
			_instance[name] = packetParserCreator;
		}

		static PacketParser* get(const std::string& name) { 
			PacketParserCreateFunc p = _instance[name] ;
			if (!p) {
				return NULL;
			}
			return p(name);
		}

	private:
		PacketParserFactory() {
			//set("base", PacketParserCreator<PacketParser>::Create); 
		}

		static PacketParserFactory _instance;
	};

	
	class PacketParser {
	public:
		PacketParser() {}
		virtual ~PacketParser() {} 
		PacketParser(PacketReader * packetReader) : packetReader(packetReader) {}

		virtual void parse(XmlPullParser*) = 0;
	protected:
		PacketReader * packetReader;
	};

	class MessagePacketParser : public PacketParser {
		void parse(XmlPullParser *);
	};
	class IQPacketParser : public PacketParser {
		void parse(XmlPullParser *);
	};
	class StreamPacketParser : public PacketParser {
		void parse(XmlPullParser *);
	};
	class FeaturePacketParser : public PacketParser {
		void parse(XmlPullParser *);
	};


    /**
     * Resets the parser using the latest connection's reader. Reseting the parser is necessary
     * when the plain connection has been secured or when a new opening stream element is going
     * to be sent by the server.
     */
    void resetParser() ;

    /**
     * Parse top-level packets in order to process them further.
     *
     * @param thread the thread that is being used by the reader to parse incoming packets.
     */
    void parsePackets();

    /**
     * Releases the connection ID lock so that the thread that was waiting can resume. The
     * lock will be released when one of the following three conditions is met:<p>
     *
     * 1) An opening stream was sent from a non XMPP 1.0 compliant server
     * 2) Stream features were received from an XMPP 1.0 compliant server that does not support TLS
     * 3) TLS negotiation was successful
     *
     */
    void releaseConnectionIDLock() ;

    /**
     * Processes a packet after it's been fully parsed by looping through the installed
     * packet collectors and listeners and letting them examine the packet to see if
     * they are a match with the filter.
     *
     * @param packet the packet to process.
     */
    void processPacket(Packet * packet) ;
	
	//apparently we don't need this 
	//bool parseStreamError(XMLParser * parser) ;

	void parseFeatures(XmlPullParser * parser) ;

	void setConnectionID(std::string id);
		
    /**
     * Returns a collection of Stings with the mechanisms included in the mechanisms stanza.
     *
     * @param parser the XML parser, positioned at the start of an IQ packet.
     * @return a collection of Stings with the mechanisms included in the mechanisms stanza.
     * @throws Exception if an exception occurs while parsing the stanza.
     */
	std::vector<std::string> parseMechanisms(XMLParser * parser) ;

	std::vector<std::string> parseCompressionMethods(XMLParser * parser);

    /**
     * Parses an IQ packet.
     *
     * @param parser the XML parser, positioned at the start of an IQ packet.
     * @return an IQ object.
     * @throws Exception if an exception occurs while parsing the packet.
     */
    IQ * parseIQ(XMLParser * parser) ;	

	Authentication * parseAuthentication(XMLParser * parser) ;

	RosterPacket * parseRoster(XMLParser * parser) ;

	Registration * parseRegistration(XMLParser * parser) ;
	
	Bind * parseResourceBinding(XMLParser * parser) ;

	XMPPConnection * connection;
	XmlPullParser * parser;
	bool done;
	std::vector<PacketCollector*> collectors;
	std::map<PacketListener*, ListenerWrapper*> listeners;
	std::set<ConnectionListener*> connectionListeners;
	std::string connectionID;

	//TODO  sem_t or pthread_mutex

};

#endif //PACKETREADER_H
